#ifndef LOG_H
#define LOG_H

#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <time.h>
#include <iomanip>

#include "StringBuilder.h"

class Log {
public:

    enum LOG_LEVEL {
        LEVEL_NONE, // no logging
        LEVEL_FATAL, // log errors that are fatal for the application
        LEVEL_ERROR, // log errors
        LEVEL_WARN, // log warnings
        LEVEL_INFO, // log informations
        LEVEL_DEBUG, // log everything

        NUMBER_OF_LEVELS // overall number of log levels
    };

    enum LOG_TARGET {
        TARGET_CONSOLE, // write log to console
        TARGET_FILE, // write log to file

        NUMBER_OF_TARGETS // overall number of possible log targets
    };

protected:
    LOG_LEVEL targetLevels[NUMBER_OF_TARGETS];
    std::string levelNames[NUMBER_OF_LEVELS];
    time_t rawtime;
    std::ofstream outputFileStream;

    virtual void Init(const std::string &outputFile) {
        SetLogLevel(TARGET_CONSOLE, LEVEL_DEBUG);
        SetLogLevel(TARGET_FILE, LEVEL_WARN);

        levelNames[LEVEL_NONE] = "NONE";
        levelNames[LEVEL_FATAL] = "FATAL";
        levelNames[LEVEL_ERROR] = "ERROR";
        levelNames[LEVEL_WARN] = "WARN";
        levelNames[LEVEL_INFO] = "INFO";
        levelNames[LEVEL_DEBUG] = "DEBUG";

        outputFileStream.open(outputFile.c_str(), std::ios_base::app);
    }

    std::string GetTimeString() {
        time(&rawtime);
        struct tm ti;
        localtime_s(&ti, &rawtime);
        std::stringstream ss;
        ss << "[" <<
            ti.tm_year + 1900 << "-" << std::setfill<char>('0') <<
            std::setw(2) << ti.tm_mon << "-" <<
            std::setw(2) << ti.tm_mday << " " <<
            std::setw(2) << ti.tm_hour << ":" <<
            std::setw(2) << ti.tm_min << ":" <<
            std::setw(2) << ti.tm_sec << "]";
        return ss.str();
    }

    virtual void LogForLevel(LOG_LEVEL level, const std::string &s) {
        if (targetLevels[TARGET_CONSOLE] >= level) {
            std::cout
                << std::endl
                << GetTimeString() << ' '
                << std::setfill<char>(' ') << std::setw(5) << levelNames[level]
                << ": "
                << s << std::endl;
        }

        if (targetLevels[TARGET_FILE] >= level) {
            outputFileStream
                << std::endl
                << GetTimeString() << ' '
                << std::setfill<char>(' ') << std::setw(5) << levelNames[level]
                << ": "
                << s << std::endl;
        }
    }

public:

    Log() {
        Init("log.txt");
    }

    Log(std::string outputFile) {
        Init(outputFile);
    }

    virtual ~Log() {
        if (outputFileStream.is_open()) {
            outputFileStream.close();
        }
    }

    void SetLogLevel(LOG_TARGET target, LOG_LEVEL level) {
        targetLevels[target] = level;
    }

    inline void LogFatal(const std::string &s) {
        LogForLevel(LEVEL_FATAL, s);
    }

    inline void LogError(const std::string &s) {
        LogForLevel(LEVEL_ERROR, s);
    }

    inline void LogWarn(const std::string &s) {
        LogForLevel(LEVEL_WARN, s);
    }

    inline void LogInfo(const std::string &s) {
        LogForLevel(LEVEL_INFO, s);
    }

    inline void LogDebug(const std::string &s) {
        LogForLevel(LEVEL_DEBUG, s);
    }
};

#endif //LOG_H
